package org.ourgrid.broker.commands.executors;

import java.util.LinkedHashMap;
import java.util.Map;

import org.jivesoftware.smackx.filetransfer.FileTransfer.Status;
import org.ourgrid.broker.commands.SchedulerData;
import org.ourgrid.broker.controller.operations.GetOperation;
import org.ourgrid.broker.controller.operations.InitOperation;
import org.ourgrid.broker.dao.JobDAO;
import org.ourgrid.broker.dao.PeerDAO;
import org.ourgrid.broker.dao.WorkerDAO;
import org.ourgrid.common.interfaces.AccountingAggregator;
import org.ourgrid.common.interfaces.to.GridProcessAccounting;
import org.ourgrid.common.interfaces.to.GridProcessHandle;
import org.ourgrid.common.interfaces.to.GridProcessPhasesData;
import org.ourgrid.common.interfaces.to.GridProcessResultInfo;
import org.ourgrid.common.interfaces.to.GridProcessState;
import org.ourgrid.common.interfaces.to.RequestSpec;
import org.ourgrid.common.interfaces.to.TransferTime;
import org.ourgrid.common.spec.job.JobSpec;
import org.ourgrid.common.spec.worker.WorkerSpec;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;

import br.edu.ufcg.lsd.commune.container.servicemanager.ServiceManager;
import br.edu.ufcg.lsd.commune.identification.ContainerID;
import br.edu.ufcg.lsd.commune.identification.DeploymentID;
import br.edu.ufcg.lsd.commune.processor.filetransfer.IncomingTransferHandle;
import br.edu.ufcg.lsd.commune.processor.filetransfer.OutgoingTransferHandle;
import br.edu.ufcg.lsd.commune.processor.filetransfer.TransferHandle;
import br.edu.ufcg.lsd.commune.processor.filetransfer.TransferProgress;

public class ReportReplicaAccountingExecutor extends SchedulerCommandExecutor {
	
	private int jobID;
	private int requiredWorkers;
	private int maxFails;
	private int maxReplicas;

	
	/**
	 * <REPORT_ACCOUNTING peerID='String' workerID='String' state='String' initCPUTime='long'
	 *                    requestID='long' taskSequenceNumber='int' creationTime='long'
	 *                    latestPhase='String' sabotageCheck='String' jobID='int' requiredWorkers='int'
	 *         			  maxFails='int' maxReplicas='int'/>
	 *                    
	 *      <PHASES_DATA initBeginning='long' initEnd='long' remoteBeginning='long' remoteEnd='long'
	 *                   finalBeginning='long' finalEnd='long'>
	 *                   
	 *            1..n <INIT_OPERATION jobID='int' taskID='int' processID='int' requestID='long' 
	 *                                 workerID='String' localFilePath='String' remoteFilePath='String' 
	 *                                 transferDescription='String' initTime='long' endTime='long' />
	 *                                 
	 *            1..n <GET_OPERATION jobID='int' taskID='int' processID='int' requestID='long' 
	 *                                 workerID='String' localFilePath='String' remoteFilePath='String' 
	 *                                 transferDescription='String' initTime='long' endTime='long' />
	 *                    
	 *      </PHASES_DATA>              
	 * 
	 *      <PROCESS_RESULT_INFO exitValue='int' errorCause='String' executionErrorType='String'
	 *                           stderr='String' stdout='String'/>
	 *                           
	 * 1..n <PEER_BALANCE attribute='String' value='String' />
	 *                     
	 * 1..n	<TRANSFER_PROGRESS handleID='long' localFileName='String' destinationFileName='String' 
	 *                         fileSize='long' description='String'
	 *                         id='String' newStatus='String' amountWritten='long' progress='double'
	 *                         transferRate='double' outgoing='boolean'/>
	 * 
	 * </REPORT_ACCOUNTING>
	 */
	public void execute(SchedulerData data, ServiceManager manager) {
		
		GridProcessAccounting acc = getData(data.getXml(), manager);
		
		Document logDocFile = super.getXMl(data.getXml());
		Element element = logDocFile.getDocumentElement();
		
		String peerID = element.getAttribute("peerID");
		DeploymentID peerDeploymentID = new DeploymentID(peerID);
		
		AccountingAggregator aggregator = manager.getDAO(
				PeerDAO.class).getAccountingAgreggator(peerDeploymentID.getContainerID());
		
		aggregator.reportReplicaAccounting(acc);
	}
	
	private GridProcessAccounting getData(String xml, ServiceManager manager) {
		
		Document logDocFile = super.getXMl(xml);
		Element element = logDocFile.getDocumentElement();
		
		long requestID = Long.valueOf(element.getAttribute("requestID"));
		
		jobID = Integer.valueOf(element.getAttribute("jobID"));
		requiredWorkers = Integer.valueOf(element.getAttribute("requiredWorkers"));
		maxFails = Integer.valueOf(element.getAttribute("maxFails"));
		maxReplicas = Integer.valueOf(element.getAttribute("maxReplicas"));

		String workerID = element.getAttribute("workerID");
		DeploymentID workerDeploymentID = new DeploymentID(workerID);
		
		JobSpec jobSpec = manager.getDAO(JobDAO.class).getJobSpec(jobID);
		
		RequestSpec requestSpec = new RequestSpec(jobID, jobSpec, requestID, jobSpec.getRequirements(), 
				requiredWorkers, maxFails, maxReplicas, manager.getMyCertPath());
		
		WorkerSpec workerSpec = manager.getDAO(WorkerDAO.class).getWorkerSpec(workerDeploymentID);
		String workerPK = manager.getDAO(WorkerDAO.class).getWorkerPublicKey(workerDeploymentID.getServiceID());
		
		GridProcessAccounting acc = new GridProcessAccounting(requestSpec, workerID, workerPK, workerSpec);
		
		long creationTime = Long.valueOf(element.getAttribute("creationTime"));
		String latestPhase = element.getAttribute("latestPhase");
		String sabotageCheck = element.getAttribute("sabotageCheck");
		int taskSequenceNumber = Integer.valueOf(element.getAttribute("taskSequenceNumber"));
		String state = element.getAttribute("state");
		
		acc.setCreationTime(creationTime);
		acc.setLatestPhase(latestPhase);
		acc.setSabotageCheck(sabotageCheck);
		acc.setTaskSequenceNumber(taskSequenceNumber);
		acc.setState(getState(state));
		
		
		//Phases data
		acc.setPhasesData(getPhasesData(element));
		
		//Result Info
		acc.setResultInfo(getResultInfo(element));
		
		//Peer Balance
		fillPeerBalance(element, acc);
		
		//Transfer Progress
		acc.setTransfersProgress(getTransferProgressMap(element));
		
		return acc;
	}

	private Map<TransferHandle, TransferProgress> getTransferProgressMap(Element element) {
		int size = element.getElementsByTagName("TRANSFER_PROGRESS").getLength();
		
		NamedNodeMap tpAttributes = null;
		long handleID;
		String localFileName;
		String destinationFileName;
		long fileSize;
		String description;
		String id;
		String newStatus;
		long amountWritten;
		double progress;
		double transferRate;
		boolean outgoing;
		TransferProgress transferProgress = null;
		TransferHandle transferHandle = null;
		
		Map<TransferHandle, TransferProgress> tpMap = new LinkedHashMap<TransferHandle, TransferProgress>();

		for (int i = 0; i < size; i++) {
			
			tpAttributes = element.getElementsByTagName("TRANSFER_PROGRESS").item(i).getAttributes();
			
			handleID = Long.valueOf(tpAttributes.getNamedItem("handleID").getNodeValue());
			localFileName = tpAttributes.getNamedItem("localFileName").getNodeValue();
			destinationFileName = tpAttributes.getNamedItem("destinationFileName").getNodeValue();
			fileSize = Long.valueOf(tpAttributes.getNamedItem("fileSize").getNodeValue());
			description = tpAttributes.getNamedItem("description").getNodeValue();
			id = tpAttributes.getNamedItem("id").getNodeValue();
			newStatus = tpAttributes.getNamedItem("newStatus").getNodeValue();
			amountWritten = Long.valueOf(tpAttributes.getNamedItem("amountWritten").getNodeValue());
			progress = Double.valueOf(tpAttributes.getNamedItem("progress").getNodeValue());
			transferRate = Double.valueOf(tpAttributes.getNamedItem("transferRate").getNodeValue());
			outgoing = Boolean.valueOf(tpAttributes.getNamedItem("outgoing").getNodeValue());
			
			if (outgoing) {
				transferHandle = new OutgoingTransferHandle(handleID, localFileName, destinationFileName, 
						description, new DeploymentID(id)); 
			} else {
				transferHandle = new IncomingTransferHandle(handleID, localFileName, fileSize, 
						destinationFileName, description, ContainerID.parse(id));
			}
			
			transferProgress = new TransferProgress(transferHandle, localFileName, fileSize, getStatus(newStatus),
					amountWritten, progress, transferRate, outgoing);
			
			tpMap.put(transferHandle, transferProgress);
		}
		
		return tpMap;
	}
	
	private void fillPeerBalance(Element element, GridProcessAccounting acc) {
		int size = element.getElementsByTagName("PEER_BALANCE").getLength();
		
		NamedNodeMap pbAttributes = null;
		String property = null;
		double value = 0;
		
		for (int i = 0; i < size; i++) {
			
			pbAttributes = element.getElementsByTagName("PEER_BALANCE").item(i).getAttributes();
			property = pbAttributes.getNamedItem("attribute").getNodeValue();
			value = Double.valueOf(pbAttributes.getNamedItem("value").getNodeValue());
			
			acc.setAccounting(property, value);
		}
	}
	
	private GridProcessResultInfo getResultInfo(Element element) {
		
		NamedNodeMap resultAttributes = 
			element.getElementsByTagName("PROCESS_RESULT_INFO").item(0).getAttributes();
		
		int exitValue = Integer.valueOf(resultAttributes.getNamedItem("exitValue").getNodeValue());
		String errorCause = resultAttributes.getNamedItem("errorCause").getNodeValue();
		String executionErrorType = resultAttributes.getNamedItem("executionErrorType").getNodeValue();
		String stderr = resultAttributes.getNamedItem("stderr").getNodeValue();
		String stdout = resultAttributes.getNamedItem("stdout").getNodeValue();
		
		GridProcessResultInfo resultInfo = new GridProcessResultInfo(exitValue, errorCause, executionErrorType,
				stderr, stdout);
		
		return resultInfo; 
	}
	
	private GridProcessPhasesData getPhasesData(Element element) {
		
		NamedNodeMap attributes = element.getElementsByTagName("PHASES_DATA").item(0).getAttributes();
		
		long initBeginning = Long.valueOf(attributes.getNamedItem("initBeginning").getNodeValue());
		long initEnd = Long.valueOf(attributes.getNamedItem("initEnd").getNodeValue());
		long remoteBeginning = Long.valueOf(attributes.getNamedItem("remoteBeginning").getNodeValue());
		long remoteEnd = Long.valueOf(attributes.getNamedItem("remoteEnd").getNodeValue());
		long finalBeginning = Long.valueOf(attributes.getNamedItem("finalBeginning").getNodeValue());
		long finalEnd = Long.valueOf(attributes.getNamedItem("finalEnd").getNodeValue());
		
		GridProcessPhasesData pData = new GridProcessPhasesData(initBeginning, initEnd, remoteBeginning,
				remoteEnd, finalBeginning, finalEnd);
		
		Map<InitOperation, TransferTime> initTransferProgress = getInitOperations(element);
		Map<GetOperation, TransferTime> getTransferProgress = getGetOperations(element);
		
		pData.setGetOperations(getTransferProgress);
		pData.setInitOperations(initTransferProgress);
		
		return pData;
	}
	
	
	private Map<InitOperation, TransferTime> getInitOperations(Element element) {
		
		Map<InitOperation, TransferTime> transferProgress = new LinkedHashMap<InitOperation, TransferTime>();
		fillOperationMap(element, transferProgress, null, false);
		
		return transferProgress;
	}
	
	private Map<GetOperation, TransferTime> getGetOperations(Element element) {
		
		Map<GetOperation, TransferTime> transferProgress = new LinkedHashMap<GetOperation, TransferTime>();
		fillOperationMap(element, null, transferProgress, true);
		
		return transferProgress;
	}
	
	private void fillOperationMap(Element element, Map<InitOperation, TransferTime> initTransferProgress,
			Map<GetOperation, TransferTime> getTransferProgress, boolean get) {
		
		int opSize = 0;
		
		if (get) {
			opSize = element.getElementsByTagName("GET_OPERATION").getLength();
		} else {
			opSize = element.getElementsByTagName("INIT_OPERATION").getLength();
		}
		
		NamedNodeMap opAttributes = null;
		int jobID;
		int taskID;
		int processID;
		long requestID2;
		String workerID2;
		String localFilePath;
		String remoteFilePath;
		String transferDescription;
		long initTime;
		long endTime;
		
		GetOperation getOp =  null;
		InitOperation initOp = null;
		TransferTime transferTime = null;
		
		for (int i = 0; i < opSize; i++) {
			
			if (get) {
				opAttributes = element.getElementsByTagName("GET_OPERATION").item(i).getAttributes();
			} else {
				opAttributes = element.getElementsByTagName("INIT_OPERATION").item(i).getAttributes();
			}
			
			jobID = Integer.valueOf(opAttributes.getNamedItem("jobID").getNodeValue());
			taskID = Integer.valueOf(opAttributes.getNamedItem("taskID").getNodeValue());
			processID = Integer.valueOf(opAttributes.getNamedItem("processID").getNodeValue());
			requestID2 = Long.valueOf(opAttributes.getNamedItem("requestID").getNodeValue());
			workerID2 = opAttributes.getNamedItem("workerID").getNodeValue();
			localFilePath = opAttributes.getNamedItem("localFilePath").getNodeValue();
			remoteFilePath = opAttributes.getNamedItem("remoteFilePath").getNodeValue();
			transferDescription = opAttributes.getNamedItem("transferDescription").getNodeValue();
			initTime = Long.valueOf(opAttributes.getNamedItem("initTime").getNodeValue());
			endTime = Long.valueOf(opAttributes.getNamedItem("endTime").getNodeValue());
			
			transferTime = new TransferTime();
			transferTime.setInitTime(initTime);
			transferTime.setEndTime(endTime);
			
			if (get) {
				getOp = new GetOperation(new GridProcessHandle(jobID, taskID, processID), requestID2, workerID2, 
						localFilePath, remoteFilePath, transferDescription, null);
				getTransferProgress.put(getOp, transferTime);
				
			} else {
				initOp = new InitOperation(new GridProcessHandle(jobID, taskID, processID), requestID2, workerID2, 
						localFilePath, remoteFilePath, transferDescription, null);
				initTransferProgress.put(initOp, transferTime);
			}
		}
	}
	
	
	private GridProcessState getState(String state) {
		
		GridProcessState value = null;
		
		if (state.equals("FAILED")) {
			value = GridProcessState.FAILED;
		} else if (state.equals("FINISHED")) {
			value = GridProcessState.FINISHED;
		} else if (state.equals("ABORTED")) {
			value = GridProcessState.ABORTED;
		} else if (state.equals("CANCELLED")) {
			value = GridProcessState.CANCELLED;
		} else if (state.equals("UNSTARTED")) {
			value = GridProcessState.UNSTARTED;
		} else if (state.equals("RUNNING")) {
			value = GridProcessState.RUNNING;
		} else if (state.equals("SABOTAGED")) {
			value = GridProcessState.SABOTAGED;
		}
		
		return value;
	}
	
	private Status getStatus(String status) {
		
		Status value = null;
		
		if (status.startsWith("cancelled")) {
			value = Status.cancelled;
		} else if (status.startsWith("complete")) {
			value = Status.complete;
		} else if (status.startsWith("error")) {
			value = Status.error;
		} else if (status.startsWith("in_progress")) {
			value = Status.in_progress;
		} else if (status.startsWith("initial")) {
			value = Status.initial;
		} else if (status.startsWith("negotiated")) {
			value = Status.negotiated;
		} else if (status.startsWith("negotiating_stream")) {
			value = Status.negotiating_stream;
		} else if (status.startsWith("negotiating_transfer")) {
			value = Status.negotiating_transfer;
		} else if (status.startsWith("refused")) {
			value = Status.refused;
		}
		
		return value;
	}
}
